﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;

namespace JSKE
{
    public delegate void BreaktimeDbClickHandler (BreakTime breaktime);
    public delegate void BreaktimeKeyDownHandler(BreakTime breaktime, KeyEventArgs e);
    public delegate void OnFocusHandler(WorkingHoursBar whb);
    public delegate void OnCurrentBreaktimeChangeHandler(WorkingHoursBar whb);

    public partial class WorkingHoursBar : UserControl
    {

        #region Attributes
        const int ValueOfDate = 24 * 60;
        List<InternalBreakTime> listInternalBreaktime;
        List<BreakTime> listbt;

        InternalBreakTime activeBreaktime;
        Graphics objGraphic;
        bool focus;
        #endregion

        #region Properties

        #endregion

        #region Events

        public event BreaktimeDbClickHandler BreaktimeDbClick;
        public event BreaktimeKeyDownHandler BreaktimeKeyDown;
        public event OnFocusHandler OnFocus;
        public event OnCurrentBreaktimeChangeHandler OnCurrentBreaktimeChange;

        #endregion

        #region Private method

        int gWidth { get; set; }
        int gHeight { get; set; }
        int gRight { get; set; }
        InternalBreakTime ActiveBreaktime
        {
            get
            {
                return activeBreaktime;
            }
            set
            {
                if (activeBreaktime != value)
                {
                    if (activeBreaktime != null)
                    {
                        activeBreaktime.lb3.BackColor = PassiveColor;
                    }
                    activeBreaktime = value;

                    if (activeBreaktime != null)
                        activeBreaktime.lb3.BackColor = HoverColor;

                    if (OnCurrentBreaktimeChange != null) OnCurrentBreaktimeChange(this);
                }
            }
        }
        
        public WorkingHoursBar()
        {
            InitializeComponent();

            listbt = new List<BreakTime>();
            listInternalBreaktime = new List<InternalBreakTime>();

            ActiveColor = Color.MediumSeaGreen;
            PassiveColor = Color.Gray;
            HoverColor = Color.Pink;
            FocusColor = Color.Blue;

            LeftIndent = RightIndent = 75;
            TopIndent = 0;
        }

        private void AddBreakPositive(InternalBreakTime bk)
        {
            for (int i = 0; i < listInternalBreaktime.Count; ++i)
            {
                int compare = Compare(bk.breaktime, listInternalBreaktime[i].breaktime);
                if (compare == -1)
                {
                    listInternalBreaktime.Insert(i, bk);
                    listbt.Insert(i, bk.breaktime);
                    return;
                }
                else if (compare == 0)
                    throw new BreaktimeException("There are some BreakTime intercep with adding BreakTime, Please check again!!!");
            }
            listInternalBreaktime.Add(bk);
            listbt.Add(bk.breaktime);
        }
        private int Compare(BreakTime bt1, BreakTime bt2)
        {
            if (bt1.End < bt2.Start) return -1;
            if (bt2.End < bt1.Start) return 1;
            return 0;
        }

        private void Delete(InternalBreakTime bk)
        {
            this.Controls.Remove(bk.lb3);

            listInternalBreaktime.Remove(bk);
            listbt.Remove(bk.breaktime);

            this.Refresh();
        }
        private void Modify(InternalBreakTime bk, BreakTime time)
        {
            int i = listInternalBreaktime.IndexOf(bk);

            if ((i != listInternalBreaktime.Count - 1 && listInternalBreaktime[i + 1].breaktime.Start <= time.End))
            {
                // xử lí việc 2 breaktime đè lên nhau: BreakTime thứ i có End lớn hơn Start của BreakTime thứ i+1
                throw new BreaktimeException("Breaktime conflict", bk.breaktime.End);
            }
            else if (i != 0 && time.Start <= listInternalBreaktime[i - 1].breaktime.End)
            {
                // xử lí việc 2 breaktime đè lên nhau: BreakTime thứ i-1 có End lớn hơn Start của BreakTime thứ i
                throw new BreaktimeException("Breaktime conflict", bk.breaktime.Start);
            }

            bk.breaktime = time;

            int left = time.Start.ToMinute * this.gWidth / ValueOfDate + LeftIndent;
            int width = (int)((time.End - time.Start) * this.gWidth / ValueOfDate);

            bk.lb3.Size = new Size(width, this.gHeight);
            bk.lb3.Location = new Point(left, this.TopIndent);
        }
        private void ReLayout()
        {
            gHeight = this.Height - TopIndent;
            gWidth = this.Width - LeftIndent - RightIndent;
            gRight = this.Width - RightIndent;

            if (objGraphic != null) objGraphic.Dispose();
            objGraphic = Graphics.FromHwnd(this.Handle);
            Rectangle clipRect = new Rectangle(LeftIndent, TopIndent, gWidth, gHeight);
            objGraphic.SetClip(clipRect);

            foreach (InternalBreakTime bk in listInternalBreaktime)
            {
                BreakTime time = bk.breaktime;

                int left = (time.Start.ToMinute) * this.gWidth / ValueOfDate + LeftIndent;
                int width = (int)((time.End - time.Start) * this.gWidth / ValueOfDate);

                bk.lb3.Size = new Size(width, this.gHeight);
                bk.lb3.Location = new Point(left, this.TopIndent);
            }
        }
        private void RefreshBar()
        {
            if (objGraphic == null) return;
            RefreshBar(objGraphic);
        }
        private void RefreshBar(Graphics g)
        {
            this.SuspendLayout();
            
            SolidBrush activeBrush = new SolidBrush(this.Enabled ? ActiveColor: PassiveColor);
            SolidBrush focusBrush = new SolidBrush(FocusColor);
            Point p = new Point(this.LeftIndent, this.TopIndent);
            Size s = new Size(this.gWidth, this.gHeight);

            g.FillRectangle(activeBrush, new Rectangle(p, s));
            if (focus) g.DrawRectangle(new Pen(focusBrush, 5), new Rectangle(p, s));
            
            this.ResumeLayout();
        }

        #endregion

        #region Event Handlers
        
        private void WorkingHoursBar_Load(object sender, EventArgs e)
        {
            ActiveBreaktime = null;
            RefreshBar();
        }
        private void WorkingHoursBar_Paint(object sender, PaintEventArgs e)
        {
            RefreshBar();   
        }
        private void WorkingHoursBar_Click(object sender, EventArgs e)
        {
            ActiveBreaktime = null;
        }
        private void WorkingHoursBar_KeyDown(object sender, KeyEventArgs e)
        {
            Console.WriteLine(this.Name);
            if (BreaktimeKeyDown != null && activeBreaktime != null)
            {
                BreaktimeKeyDown(activeBreaktime.breaktime, e);
            }
        }
        private void WorkingHoursBar_Leave(object sender, EventArgs e)
        {
            RefreshBar();
        }
        private void WorkingHoursBar_Enter(object sender, EventArgs e)
        {
            foreach (Control con in this.Parent.Controls)
            {
                WorkingHoursBar whb = con as WorkingHoursBar;
                if (whb != null && whb != this)
                {
                    whb.focus = false;
                    whb.ActiveBreaktime = null;
                    whb.RefreshBar();
                }
            }
            focus = true;
            RefreshBar();
            if (OnFocus != null) OnFocus(this);
        }
        private void WorkingHoursBar_Resize(object sender, EventArgs e)
        {
            ReLayout();
            Refresh();
        }

        private void dtpStart_ValueChanged(object sender, EventArgs e)
        {
            try
            {
                ModifyStartBreak(new BKTimePoint(dtpStart.Value));
            }
            catch (BreaktimeException ex)
            {
                (dtpStart as MyDateTimePicker).RollBack();
                MessageBox.Show(ex.Message);
            }
        }
        private void dtpEnd_ValueChanged(object sender, EventArgs e)
        {
            try
            {
                // chú ý là nếu giá trị dtpEnd đang là 11:59 PM , chọn 11 và nhấn up button thì sẽ thành 12:59 AM
                // sẽ có thể dẫn đến throw exception do có các breaktime intercep với nhau
                ModifyLastBreak(new BKTimePoint(dtpEnd.Value));
            }
            catch (BreaktimeException ex)
            {
                (dtpEnd as MyDateTimePicker).RollBack();
                MessageBox.Show(ex.Message);
            }
        }

        private void bk_Click(object sender, EventArgs e)
        {
            ActiveBreaktime = (InternalBreakTime)(sender as Label).Tag;
            this.Focus();
        }
        private void bk_MouseEnter(object sender, EventArgs e)
        {
            Label lb = sender as Label;
            lb.BackColor = HoverColor;
        }
        private void bk_MouseLeave(object sender, EventArgs e)
        {
            Label lb = sender as Label;
            if (lb.Tag != activeBreaktime)
                lb.BackColor = PassiveColor;
        }
        private void bk_DoubleClick(object sender, EventArgs e)
        {
            ActiveBreaktime = (InternalBreakTime)(sender as Label).Tag;
            if (BreaktimeDbClick != null)
                BreaktimeDbClick(activeBreaktime.breaktime);
        }
        
        #endregion

        #region public method

        [DefaultValue(typeof(Color), "Pink")]
        public Color HoverColor { get; set; }
        [DefaultValue(typeof(Color), "MediumSeaGreen")]
        public Color ActiveColor { get; set; }
        [DefaultValue(typeof(Color), "Gray")]
        public Color PassiveColor { get; set; }
        [DefaultValue(typeof(Color), "Blue")]
        public Color FocusColor { get; set; }

        [DefaultValue(75)]
        public int LeftIndent { get; set; }
        [DefaultValue(75)]
        public int RightIndent { get; set; }
        [DefaultValue(0)]
        public int TopIndent { get; set; }


        public void ModifyStartBreak(BKTimePoint time)
        {
            if (listInternalBreaktime.Count > 0 &&
                listInternalBreaktime[0].breaktime.IsFirstBreakOfDay())
            {
                if (time.ToMinute == 0)
                    Delete(listInternalBreaktime[0]);
                else
                    Modify(listInternalBreaktime[0], new BreakTime(new BKTimePoint(0, 0), time));
            }
            else
            {
                if (time.ToMinute != 0)
                    AddBreak(new BKTimePoint(0, 0), time);
            }
        }
        public void ModifyLastBreak(BKTimePoint time)
        {
            if (listInternalBreaktime.Count > 0 &&
                listInternalBreaktime[listInternalBreaktime.Count - 1].breaktime.IsEndBreakOfDay())
            {
                if (time.ToMinute == 0)
                    Delete(listInternalBreaktime[listInternalBreaktime.Count - 1]);
                else
                    Modify(listInternalBreaktime[listInternalBreaktime.Count - 1], new BreakTime(time, new BKTimePoint(24, 0)));
            }
            else
            {
                if (time.ToMinute != 0)
                    AddBreak(time, new BKTimePoint(24, 0));
            }
        }
        public BreakTime CurrentBreaktime
        {
            get
            {
                if (activeBreaktime != null)
                    return activeBreaktime.breaktime;
                else 
                    return null;
            }
        }
        public List<BreakTime> GetBreaktimeList()
        {
            List<BreakTime> list = new List<BreakTime>(10);
            foreach (InternalBreakTime bk in listInternalBreaktime)
            {
                BreakTime bt = new BreakTime(bk.breaktime.Start, bk.breaktime.End);
                list.Add(bt);
            }
            return list;
        }
        public bool IsOffDay
        {
            get
            {
                return listInternalBreaktime.Count == 1 &&
                    listInternalBreaktime[0].breaktime.IsEndBreakOfDay() &&
                    listInternalBreaktime[0].breaktime.IsFirstBreakOfDay();
            }
            set
            {
                this.DeleteAllBreaktime();
                if (!value)
                    this.AddBreak(new BKTimePoint(0, 0), new BKTimePoint(24, 0));

                ActiveBreaktime = null;
                this.Enabled = value;
            }
        }
        public void ReloadDateTimePicker()
        {
            if (listbt.Count > 0)
            {
                BreakTime firstBreakTime = listbt[0];
                BreakTime lastBreakTime = listbt[listbt.Count-1];
                if (firstBreakTime.IsFirstBreakOfDay())
                {
                    dtpStart.Value = firstBreakTime.End.ToDateTime;
                }
                else
                {
                    dtpStart.Value = new BKTimePoint(0, 0).ToDateTime;
                }
                if (lastBreakTime.IsEndBreakOfDay())
                {
                    dtpEnd.Value = lastBreakTime.Start.ToDateTime;
                }
                else
                {
                    //dtpEnd.Value = DatetimeExt.CreateTimeInDay(23, 59);
                    dtpEnd.Value = new BKTimePoint(0, 0).ToDateTime;
                }
            }
            else
            {
                dtpStart.Value = new BKTimePoint(0, 0).ToDateTime;
                dtpEnd.Value = new BKTimePoint(24, 0).ToDateTime; // DatetimeExt.CreateTimeInDay(23, 59);
            }
            if (objGraphic != null)
            {
                RefreshBar();
            }
        }

        public void AddBreak(BKTimePoint start, BKTimePoint end)
        {
            AddBreak(new BreakTime(start, end));
            this.Refresh();
        }
        public void AddBreak(BreakTime time)
        {
            time = new BreakTime(time.Start, time.End);

            InternalBreakTime bk = new InternalBreakTime();
            bk.breaktime = time;

            AddBreakPositive(bk);

            Label lb = new Label();
            bk.lb3 = lb;

            Modify(bk, time);

            lb.BackColor = PassiveColor;
            lb.MouseEnter += new EventHandler(bk_MouseEnter);
            lb.MouseLeave += new EventHandler(bk_MouseLeave);
            lb.DoubleClick += new EventHandler(bk_DoubleClick);
            lb.Click += new EventHandler(bk_Click);

            lb.Tag = bk;

            this.Controls.Add(lb);

            ActiveBreaktime = bk;
        }

        public void DeleteActive()
        {
            if (activeBreaktime != null)
            {
                Delete(activeBreaktime);

                if (activeBreaktime.breaktime.IsFirstBreakOfDay())
                    dtpStart.Value = new BKTimePoint(0, 0).ToDateTime;
                else if (activeBreaktime.breaktime.IsEndBreakOfDay())
                    dtpEnd.Value = new BKTimePoint(24, 0).ToDateTime;

                ActiveBreaktime = null;
            }
        }
        public void DeleteAllBreaktime()
        {
            foreach (InternalBreakTime bk in listInternalBreaktime)
            {
                this.Controls.Remove(bk.lb3);
            }

            listInternalBreaktime.Clear();
            listbt.Clear();

            this.Refresh();
        }
        public void ModifyActive(BreakTime time)
        {
            if (activeBreaktime == null) return;

            Modify(activeBreaktime, time);
        }
        
        #endregion

    }
    class InternalBreakTime
    {
        public BreakTime breaktime;
        public Label lb3;
    }
    public class BKTimePoint
    {
        public int Hour {get; private set;}
        public int Minute { get; private set; }

        public BKTimePoint(int hour, int minute)
        {
            bool valid = 0 <= minute && minute <= 59 && 
                (hour < 24 || (hour == 24 && minute == 0));
            if (!valid)
                throw new BreaktimeException("Invalid Time in a date - " + hour + ":" + minute);

            Hour = hour;
            Minute = minute;
        }
        public BKTimePoint(DateTime dt)
        {
            Hour = dt.Hour;
            Minute = dt.Minute;
        }
        public int ToMinute
        {
            get { return Hour * 60 + Minute; }
        }
        public DateTime ToDateTime
        {
            get { return new DateTime(1988, 11, 11, Hour % 24, Minute, 0); }
        }
        public bool IsEndDate()
        {
            return Hour == 24;
        }
        public BKTimePoint Clone()
        {
            return new BKTimePoint(Hour, Minute);
        }
        public static bool operator >=(BKTimePoint x, BKTimePoint y)
        {
            return x.ToMinute >= y.ToMinute;
        }
        public static bool operator <=(BKTimePoint x, BKTimePoint y)
        {
            return x.ToMinute <= y.ToMinute;
        }
        public static bool operator >(BKTimePoint x, BKTimePoint y)
        {
            return x.ToMinute > y.ToMinute;
        }
        public static bool operator <(BKTimePoint x, BKTimePoint y)
        {
            return x.ToMinute < y.ToMinute;
        }
        public static int operator -(BKTimePoint x, BKTimePoint y)
        {
            return x.ToMinute - y.ToMinute;
        }

        public override string ToString()
        {
            return Hour + ":" + Minute;
        }
    }
    public class BreakTime
    {
        public BreakTime(BKTimePoint start, BKTimePoint end)
        {
            if (end.ToMinute == 0) end = new BKTimePoint(24, 0);
            if (start >= end)
            {
                throw new BreaktimeException("Invalid breaktime because start of Break(" + Start + ") is not before end of Break(" + End + ")");
            }
            Start = start.Clone();
            End = end.Clone();
        }
        public BKTimePoint Start { get; private set; }
        public BKTimePoint End { get; private set; }

        public int GetMappedStartTime(int mappedStartOfDateOfBreakTime)
        {
            return mappedStartOfDateOfBreakTime + Start.ToMinute;
        }

        public int GetMappedEndTime(int mappedStartOfDateOfBreakTime)
        {
            return mappedStartOfDateOfBreakTime + End.ToMinute;
        }

        public bool IsFirstBreakOfDay()
        {
            return Start.ToMinute == 0;
        }

        public bool IsEndBreakOfDay()
        {
            //return (End.Hour == 23) && (End.Minute == 59);
            return End.IsEndDate();
        }

        public override string ToString()
        {
            return Start + " - " + End;
        }
    }

    public class BreaktimeException : Exception
    {
        private BKTimePoint _oldDateTimeValue;

        public BKTimePoint OldDateTimeValue
        {
            get { return _oldDateTimeValue; }
            set { _oldDateTimeValue = value; }
        }
        public BreaktimeException(string message, BKTimePoint oldValue)
            : base(message)
        {
            OldDateTimeValue = oldValue;
        }

        public BreaktimeException(string message)
            : base(message)
        {
        }
    }
}
